﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;

using Fusion;
using Fusion.Interface;

namespace Fusion.Tools
{

    /// <summary> 
    /// Tile-mapping tool class
    /// Provides a controllable interface, and rendering functions
    /// </summary>
    public class Tiler : Entity
    {
        #region Fields

        public TileMap map; // Tilemap to edit
        public TileMap tiles; // Generated tilemap sheet to pick tiles from
        public TileMap brush; // Current brush

        // Window entities
        public Entity tilesWindow;
        public Entity mapWindow;
        public Entity brushWindow;
        public Entity listWindow;
        public Entity dataWindow;

        // Cursor fields
        bool mapFocus;
        bool tilesFocus;
        bool brushFocus;
        Rectangle cursorFrame;
        public bool cutModifier;
        public bool maskOpaqueModifier = false;
        public bool maskTransModifier = false;

        // selection fields
		bool painting;
        bool dragging;
		bool resizing;
        int xPin; // location of cursor pin
        int yPin;
        Vector2 moveStart;
        Vector2 movePin;
		List<Button> edgeButtons = new List<Button>();

		Button saveBrushButton;

        // brush list fields
		public List<Entity> selections = new List<Entity>();
		public List<Entity> brushes = new List<Entity>();
		public List<Entity> maps = new List<Entity>();

        SpriteFont font;

		// Graphics stuff
		float mapFade;
		float tilesFade;
		float brushFade;

        #endregion

		#region Events

		public void SaveBrush()
		{
			brushes.Add( new TileMap( brush ) );
		}

		#endregion

		#region Constructors

		public Tiler( string sheetName, int w, int h, int cols, int rows )
        {
            font = Engine.Fonts.Get( "default" );
			brush = new TileMap( sheetName, 1, 1, 1, 1 );
			tiles = new TileMap( sheetName, w, h, cols, rows );

            brush.SetTile( 1, 1, -1 );
            // default the sheet
            int t=0;
            for( int y=0; y < 16; y++ )
                for( int x=0; x<16; x++ )
                {
                    tiles.SetTile( x, y, t );
                    t++;
                }

            mapWindow = new Entity();
			mapWindow.position = new Vector2( 384, 128 ); ;
            mapWindow.scale = Vector2.One;
            mapWindow.container = this;


            tilesWindow = new Entity();
            tilesWindow.position = new Vector2( 80, 80 );
            tilesWindow.scale = Vector2.One;
            tilesWindow.container = this;

            brushWindow = new Entity();
            brushWindow.position = new Vector2( 128, 512 );
            brushWindow.scale = Vector2.One;
            brushWindow.container = this;

			// Buttons
			for( int i=0; i < 8; i++ )
				edgeButtons.Add( new Button( Vector2.Zero, Vector2.One * 16, "Buttons", new Rectangle( 0, 0, 64, 64 ), null, null ) );
			saveBrushButton = new Button( Vector2.Zero, Vector2.One * 16, "Buttons", new Rectangle( 0, 0, 64, 64 ), SaveBrush, null );
			saveBrushButton.container = this;
        }

		#endregion

		#region Methods

		/// <summary>
		/// Provides input to the tiler
		/// </summary>
		/// <param name="cursor">cursor position, in the same worldspace as the tiler's position</param>
		/// <param name="btnA">The state of Button A, 'paint'</param>
		/// <param name="btnB">The state of Button B, 'select'</param>
		/// <param name="btnC">The state of Button C, 'move'</param>
		/// <param name="wheelDirection">The direction the scroller has moved in.</param>
		public void DoInput( Vector2 cursor, bool btnA, bool btnB, bool btnC, int wheelDirection )
		{
			#region Drag map around

			if( btnC == true )
            {
                Vector2 dist = cursor - movePin;
                if( mapFocus == true )
                    mapWindow.position = moveStart + dist;
                else if( brushFocus == true )
                    brushWindow.position = moveStart + dist;
                else if( tilesFocus == true )
                    tilesWindow.position = moveStart + dist;

                return; // we don't want any other input if we're dragging, that might fuck things up.
            }
            else if( resizing == false )
            {
                if( mapFocus == true )
                    moveStart = mapWindow.position;
                else if( brushFocus == true )
                    moveStart = brushWindow.position;
                else if( tilesFocus == true )
                    moveStart = tilesWindow.position;
                movePin = cursor;
			}

			#endregion

			#region Check focus

			// tiles first, since they are higher
			Vector2 tilesTL = new Vector2( tilesWindow.Position.X - tiles.DrawSize.X / 2, tilesWindow.Position.Y - tiles.DrawSize.Y / 2 );
			Vector2 tilesBR = tilesTL + tiles.DrawSize;
            tilesTL -= Vector2.One * 32;
            tilesBR += Vector2.One * 32;

            Vector2 proj = Vector2.Zero;

            // default to map focus
            mapFocus = true;
            // check for tiles focus
			if( Hax.Intersect( cursor, tilesWindow.Position, tiles.DrawSize + Vector2.One * 64, ref proj ) )
            {
                tilesFocus = true;
                mapFocus = false;
            }
            else
                tilesFocus = false;
            // check for brush focus
			if( tilesFocus == false && Hax.Intersect( cursor, brushWindow.Position, brush.DrawSize + Vector2.One * 32, ref proj ) )
            {
                brushFocus = true;
                tilesFocus = false;
                mapFocus = false;
            }
            else
                brushFocus = false;

			#endregion

			#region Check resizing
			
			if( btnA == false)
			{
				resizing = false;
				for( int i = 0; i < 8; i++ )
				{
					edgeButtons[i].hovered = false;
					edgeButtons[i].pressed = false;
				}
			}
			else if( mapFocus == true && painting == false )
			{
				if( resizing == false )
					for( int i = 0; i < 8; i++ )
						edgeButtons[i].Control( cursor, btnA );

				for( int i = 0; i < 8; i++ )
					if( edgeButtons[i].pressed == true )
					{
						if( resizing == false )
						{
							resizing = true;
							movePin = cursor;
						}

						Vector2 moveDistance = cursor - movePin;
						int xMove = (int)( moveDistance.X / tiles.CelSize.X );
						int yMove = (int)( moveDistance.Y / tiles.CelSize.Y );

						// do resizing
						if( xMove != 0 || yMove != 0 )
						{
							switch( i )
							{
								case 0: // Top Left
									map.Resize( -xMove, -yMove, 0, 0 );
									break;

								case 1: // Top Middle
									map.Resize( 0, -yMove, 0, 0 );
									break;

								case 2: // Top Right
									map.Resize( 0, -yMove, xMove, 0 );
									break;

								case 3: // Middle Left
									map.Resize( -xMove, 0, 0, 0 );
									break;

								case 4: // Middle Right
									map.Resize( 0, 0, xMove, 0 );
									break;

								case 5: // Bottom Left
									map.Resize( -xMove, 0, 0, yMove );
									break;

								case 6: // Bottom Middle
									map.Resize( 0, 0, 0, yMove );
									break;

								case 7: // Bottom Right
									map.Resize( 0, 0, xMove, yMove );
									break;
							}

							mapWindow.position += new Vector2( xMove, yMove ) * (map.CelSize / 2);
							// reset movepin
							if( xMove != 0 )
								movePin.X = cursor.X;
							if( yMove != 0 )
								movePin.Y = cursor.Y;
						}

						return;
					}
			}

			#endregion

			#region Check cursor state
			// set cursor position
            if( tilesFocus == true )
            {
                cursor -= tilesWindow.Position;
                //cursor += tiles.DrawSize / 2;
            }
            else
            {
                cursor -= mapWindow.Position;
                //cursor += map.DrawSize / 2;
            }
            int xCursor;
            int yCursor;
            if( tilesFocus == true )
            {
                xCursor = (int)( cursor.X / tiles.CelSize.X );
                yCursor = (int)( cursor.Y / tiles.CelSize.Y );
            }
            else
            {
                xCursor = (int)( cursor.X / map.CelSize.X );
                yCursor = (int)( cursor.Y / map.CelSize.Y );
            }

            // set pin position
            if( dragging == false )
            {
                xPin = xCursor;
                yPin = yCursor;
            }
			

            // set cursor frame
            Vector2 min = new Vector2( Math.Min( xPin, xCursor ), Math.Min( yPin, yCursor ) );
            Vector2 max = new Vector2( Math.Max( xPin, xCursor ), Math.Max( yPin, yCursor ) );
            cursorFrame.X = (int)min.X;
            cursorFrame.Y = (int)min.Y;
            cursorFrame.Width = 1 + (int)( max.X - min.X );
			cursorFrame.Height = 1 + (int)( max.Y - min.Y );

			// update dragging state
			if( btnB == true )
				dragging = true;
			else
			{
				if( dragging == true )
					Select();
				dragging = false;
			}

			// paint
			if( btnA == true )
			{
				Paint();
				painting = true;
			}
			else
				painting = false;

			#endregion


			// update controls
			if( dragging == false )
			{
				saveBrushButton.hovered = false;
				saveBrushButton.pressed = false;
				saveBrushButton.Control( cursor, btnA );
				if( saveBrushButton.hovered == true )
				{
					SaveBrush();
				}
			}
        }

        /// <summary> Updates the tiler </summary>
        public override void Update()
        {
           // tiles.scale = Vector2.One * 2; // BUGS HERE! fix latr

            if( mapFocus == true )
                mapFade = Hax.Step( mapFade, 0.64f, Engine.period );
            else
                mapFade = Hax.Step( mapFade, 0.40f, Engine.period );

            if( tilesFocus == true )      tilesFade = Hax.Step( tilesFade, 0.64f, Engine.period );
            else tilesFade = Hax.Step( tilesFade, 0.40f, Engine.period );

            if( brushFocus == true ) brushFade = Hax.Step( brushFade, 0.64f, Engine.period );
            else brushFade = Hax.Step( brushFade, 0.40f, Engine.period );

			// corner buttons
			for( int i=0; i < 8; i++ )
				edgeButtons[i].Update();
			saveBrushButton.Update();
        }

		/// <summary> Renders the target or tilesheet with a background box, as well as the selection frame </summary>
		public override void Draw()
        {
			#region Calculate common values
			Vector2 mapTL = new Vector2( mapWindow.Position.X , mapWindow.Position.Y  );
            Vector2 tilesTL = new Vector2( tilesWindow.Position.X , tilesWindow.Position.Y  );
            Vector2 brushTL = new Vector2( brushWindow.Position.X , brushWindow.Position.Y  );
            Rectangle sr;
            if( tilesFocus == true )
                sr = new Rectangle( (int)( cursorFrame.X * tiles.CelSize.X ), (int)( cursorFrame.Y * tiles.CelSize.Y )
                    , (int)( cursorFrame.Width * tiles.CelSize.X ), (int)( cursorFrame.Height * tiles.CelSize.Y ) );
            else
                sr = new Rectangle( (int)(cursorFrame.X * map.CelSize.X), (int)(cursorFrame.Y * map.CelSize.Y)
                    , (int)(cursorFrame.Width * map.CelSize.X), (int)(cursorFrame.Height * map.CelSize.Y) );

            if( mapFocus == true )
            {
                sr.X += (int)mapTL.X;
                sr.Y += (int)mapTL.Y;
            }
            else if( tilesFocus == true )
            {
                sr.X += (int)tilesTL.X;
                sr.Y += (int)tilesTL.Y;
			}
			#endregion

			#region Draw Map

			// todo: map repeats
			UI.DrawBlock( new Rectangle( (int)mapTL.X - 32, (int)mapTL.Y - 32, (int)map.DrawSize.X + 64, (int)map.DrawSize.Y + 64 ), 0, new Color( 0, 0, 0, mapFade ) ); // plate

			Vector2 mp = map.position;
			map.position = Vector2.Zero;
            map.Draw( mapWindow ); // map
			map.position = mp;

            UI.DrawBox( new Rectangle( (int)mapTL.X, (int)mapTL.Y, (int)map.DrawSize.X, (int)map.DrawSize.Y ), Color.White ); // border

            Engine.spriteBatch.DrawString( font, "Map", mapTL, Color.White, 0, new Vector2( 0, 36 ), 1, SpriteEffects.None, 1.0f ); // header
            // todo: grid


            // Draw brush actual
            if( dragging == false && mapFocus == true && resizing == false )
            {
                // todo: draw brush at cursor
                if( tilesFocus == true )
                    brush.scale = tiles.Scale;
                else
                    brush.scale = map.Scale;
				brush.position = new Vector2( sr.X, sr.Y );// +brush.DrawSize / 2;
                brush.Draw();
                brush.position = Vector2.Zero;
            }
            brush.scale = Vector2.One;

			// draw resize buttons
			if( mapFocus == true )
			{
				edgeButtons[0].position.X = mapTL.X - 16;
				edgeButtons[0].position.Y = mapTL.Y - 16;

				edgeButtons[1].position.X = mapTL.X + map.DrawSize.X / 2;
				edgeButtons[1].position.Y = mapTL.Y - 16;

				edgeButtons[2].position.X = mapTL.X + map.DrawSize.X + 16;
				edgeButtons[2].position.Y = mapTL.Y - 16;

				edgeButtons[3].position.X = mapTL.X - 16;
				edgeButtons[3].position.Y = mapTL.Y + map.DrawSize.Y / 2;

				edgeButtons[4].position.X = mapTL.X + map.DrawSize.X + 16;
				edgeButtons[4].position.Y = mapTL.Y + map.DrawSize.Y / 2;

				edgeButtons[5].position.X = mapTL.X - 16;
				edgeButtons[5].position.Y = mapTL.Y + map.DrawSize.Y + 16;

				edgeButtons[6].position.X = mapTL.X + map.DrawSize.X / 2;
				edgeButtons[6].position.Y = mapTL.Y + map.DrawSize.Y + 16;

				edgeButtons[7].position.X = mapTL.X + map.DrawSize.X + 16;
				edgeButtons[7].position.Y = mapTL.Y + map.DrawSize.Y + 16;

				for( int i=0; i < 8; i++ )
					edgeButtons[i].Draw();
			}

			#endregion

			#region Draw Current Brush
			if( brush != null )
            {
                UI.DrawBlock( new Rectangle( (int)brushTL.X - 16, (int)brushTL.Y - 16, (int)brush.DrawSize.X + 32, (int)brush.DrawSize.Y + 32 ), 0, new Color( 0, 0, 0, brushFade ) ); // plate
                brush.Draw( brushWindow ); // map
                Engine.spriteBatch.DrawString( font, "Brush", brushTL, Color.White, 0, new Vector2( 24, 32 ), 1, SpriteEffects.None, 1.0f ); // header

				//save button
				saveBrushButton.position = new Vector2( 64, -24 );
				saveBrushButton.Draw();
			}
			#endregion

			#region Draw Tile Picker
			UI.DrawBlock( new Rectangle( (int)tilesTL.X - 32, (int)tilesTL.Y - 32, (int)tiles.DrawSize.X + 64, (int)tiles.DrawSize.Y + 64 ), 0, new Color( 0, 0, 0, tilesFade ) ); // plate
            tiles.Draw( tilesWindow );
			UI.DrawBox( new Rectangle( (int)tilesTL.X, (int)tilesTL.Y, (int)tiles.DrawSize.X, (int)tiles.DrawSize.Y ), Color.White ); // border
            Engine.spriteBatch.DrawString( font, "Tiles", tilesTL, Color.White, 0, new Vector2( 0, 36 ), 1, SpriteEffects.None, 1.0f ); // header

            // Draw selection rect
			if( brushFocus == false && resizing == false )
            {
                if( dragging == false )
                {
                    if( tilesFocus == true )
						brush.scale = tiles.Scale;
                    else
						brush.scale = map.Scale;

                    sr.Width = (int)brush.DrawSize.X;
                    sr.Height = (int)brush.DrawSize.Y;
                }
                UI.DrawBox( sr, Color.White );
                if( dragging == true )
                    UI.DrawBlock( sr, 0, new Color( 1, 1, 1, 0.64f ) );
            }
            brush.scale = Vector2.One;
			#endregion
        }

#endregion

		#region Editing

		/// <summary> Copies from the target into a new brush from the frame rectangle </summary>
        public void Select()
        {
            if( mapFocus == true )
                brush.Copy( map, cursorFrame, cutModifier );
            else if( tilesFocus == true )
                brush.Copy( tiles, cursorFrame, false ); // copy from generic frame
            brush.sheet = tiles.sheet;

			// add selection to list of 'em
			selections.Add( new TileMap( brush ) );
			if( selections.Count >= 64 )
				selections.RemoveAt( 0 );
        }

        /// <summary> Paints the brush onto the target at the frame rectangle </summary>
        public void Paint()
        {
            if( mapFocus == false )
                return;

            if( maskTransModifier == false && maskOpaqueModifier == false )
                brush.Paint( map, cursorFrame.X, cursorFrame.Y );
            else if( maskTransModifier == true )
                brush.Paint( map, cursorFrame.X, cursorFrame.Y, true );
            else if( maskOpaqueModifier == true )
                brush.Paint( map, cursorFrame.X, cursorFrame.Y, false );
            else
                return; // you're dumb
        }

        #endregion
    }
}
